/*
License: The Calos' License
Author: Shuntao Chen,
Link: https://www.caloch.cn
Privilege is hereby granted, free of use, this is an MVVM framework that I created, it is light enough to help a lot without any reliance on the other libraries, but please do keep all these info integrate when you use it! Any disobey will cause certain law issues, 
**/

//utils//

(function (w) {

    function loadScript(path) {
        var node = document.createElement('script')
        node.type = 'text/javascript'
        node.src = path + '?_=' + (new Date().getMilliseconds())
        document.body.appendChild(node)
    }

    function loadScripts() {
        for (let i = 0; i < arguments.length; i++) {
            const el = arguments[i];
            loadScript(el)
        }
    }

    function redirect(path) {
        location.href = path
    }

    function getHtmlOrJson(url, success) {
        function makeHttpObject() {
            if ("XMLHttpRequest" in window) return new XMLHttpRequest();
            else if ("ActiveXObject" in window) return new ActiveXObject("Msxml2.XMLHTTP");
        }

        var request = makeHttpObject();
        request.open("GET", url + "?" + new Date().getMilliseconds(), true);
        request.send(null);
        request.onreadystatechange = function () {
            if (request.readyState == 4) {
                success(request.responseText)
            }
        };
    }


    function getQueryJson(route) {
        let arr = route.split("?")[1].split("&");
        let queryJson = {};
        for (let i of arr) {
            queryJson[i.split("=")[0]] = i.split("=")[1];
        }
        return queryJson
    }

    function whenready(onload) {
        document.body.onload = onload
    }

    //intended as a loaded scripts manager to avoid additional load of same file
    window.loadedScripts = [];


    function require(file, callback) {
        if (window.loadedScripts.indexOf(file) === -1) {
            // create script element
            var script = document.createElement("script");
            script.src = file;
            // monitor script loading
            // IE < 7, does not support onload
            if (callback) {
                script.onreadystatechange = function () {
                    if (script.readyState === "loaded" || script.readyState === "complete") {
                        // no need to be notified again
                        script.onreadystatechange = null;
                        window.loadedScripts.push(file)
                        // notify user
                        callback();
                    }
                };
                // other browsers
                script.onload = function () {
                    callback();
                };
            }
            // append and execute script
            document.documentElement.firstChild.appendChild(script);
        }
    }



    function requireAll(scripts, next) {
        let promises = [];
        scripts.filter(s => window.loadedScripts.indexOf(s) === -1).forEach(function (url) {
            var loader = new Promise(function (resolve, reject) {
                let script = document.createElement('script');
                script.src = url;
                script.async = false;
                script.onload = function () {
                    window.loadedScripts.push(url)
                    resolve(url);
                };
                script.onerror = function () {
                    reject(url);
                };
                document.body.appendChild(script);
            });
            promises.push(loader);
        });

        return Promise.all(promises)
            .then(function () {
                console.log('all scripts loaded');
                next()
            }).catch(function (script) {
                console.log(script + ' failed to load');
            });
    }

    function makeCalo(o, loadRouter) {
        window.calo = {
            ...window.calo,
            ...o
        }
        if (loadRouter || o.routes) {
            calo.router(calo.routes || {}, function () {
                if (location.href.indexOf('#') != -1) {
                    const curRoute = location.href.split('#')[1]
                    calo.navigate(curRoute)
                }
            })
        }
        calo.run.apply(calo)
        return window.calo
    }


    function dragger() {}


    dragger.prototype = {
        setSrc: function (el, clone) {
            this.src = el
            this.clone = clone
            el.draggable = true
            el.addEventListener('dragstart', function (e) {
                setTimeout(() => {
                    console.log(this);
                }, 10)
            });

            el.addEventListener('dragend', function () {});
            return this
        },
        setTargets: function () {
            let $g = this
            for (var i of arguments) {
                i.addEventListener('dragenter', dragEnter);
                i.addEventListener('dragover', dragOver);
                i.addEventListener('dragleave', dragLeave);
                i.addEventListener('drop', drop);
            }

            function dragEnter() {
                this.className += " holding";
            }

            function dragOver(e) {
                e.preventDefault();
            }

            function dragLeave() {
                this.className = 'empty';
            }

            function drop() {
                console.log('drop')
                this.className = 'empty';
                let el = $g.clone ? $g.src.cloneNode(true) : $g.src
                this.append(el)
            }
        },
    }

    const utils = {
        loadScript,
        loadScripts,
        redirect,
        getQueryJson,
        getHtmlOrJson,
        whenready,
        require,
        requireAll,
        makeCalo,
        dragger
    }
    for (const i in utils) {
        w[i] = utils[i]
    }


    var utls = function () {}
    utls.prototype = utils
    w.utils = utls


})(window);


//calo.js



(function (o) {
    function renderObject(data, scope, prefix, identityPrefix) {
        for (const key in data) {
            const expEls = scope.querySelectorAll("[@field]")
            expEls.forEach(el => {
                const pth = el.getAttribute("@field")
                if (data[pth] && isValType(data[path])) {
                    el.dataset.identity = identityPrefix + "." + pth
                    SetValue(el, data[pth])
                }
            })

            if (Object.hasOwnProperty.call(data, key)) {
                const fieldValue = data[key];
                if (isValType(fieldValue)) {
                    const els = getElsByFieldName(scope, prefix + key)
                    els.forEach(el => {
                        SetValue(el, data[key])
                        el.dataset.identity = identityPrefix + "." + key
                    });
                } else if (isObjectType(fieldValue)) {
                    const els = getElsByFieldName(scope, key)
                    els.forEach(el => {
                        el.dataset.identity = identityPrefix + "." + key
                        renderObject(fieldValue, el, "", el.dataset.identity)
                    })
                } else if (isArrayType(fieldValue)) {
                    const els = scope.querySelectorAll("[\\@field^=" + key + "\\|]")
                    els.forEach(el => {
                        const subAlias = el.getAttribute("@field").split('|')[1]
                        let ci = 0
                        let lastCursor = el
                        fieldValue.forEach(val => {
                            clone = el.cloneNode(true)
                            clone.style.display = ''
                            clone.setAttribute("poped", "true")
                            clone.dataset.identity = identityPrefix + "." + key + `[${ci}]`
                            clone.dataset.index = ci
                            insertAfter(clone, lastCursor)
                            lastCursor = clone
                            if (isValType(val))
                                SetValue(clone, val)
                            else
                                renderObject(val, clone, subAlias + ".", clone.dataset.identity)
                            ci++
                            el.style.display = 'none'

                        });
                    })
                }
            }
        }

    }

    window.calo = o || {
        model: {}
    }
    window.log = x => console.log(x)

    function removePopped(root) {
        var poped = root.querySelectorAll("[poped='true']")
        poped.forEach(p => {
            // p.parentNode.removeChild(p)
            p.remove()
        })
    }
    var root = document.querySelector("[calo]");

    if (!root) {
        const div = document.createElement('div')
        div.setAttribute('calo', '')
        document.body.appendChild(div)
        root = document.querySelector("[calo]");
    }
    calo.rootel = root
    calo.refs = calo.refs || {}
    let refs = root.querySelectorAll('[ref]')
    refs.forEach(r => {
        let refName = r.getAttribute('ref')
        calo.refs[refName] = r
    })
    calo.run = function () {
        removePopped(root)
        renderObject({
            ...calo.global,
            ...calo.model
        }, root, "", "calo.model")
        var clicks = root.querySelectorAll("[\\@Click]")
        var changes = root.querySelectorAll("[\\@Change]")
        var links = document.querySelectorAll("[\\@Link]")
        clicks.forEach(c => {
            c.onclick = function (e) {
                calo[c.getAttribute("@Click")].call(calo, c, c.value)
                calo.run.apply(calo)
            }
        })
        changes.forEach(c => {
            c.onchange = function () {
                calo[c.getAttribute("@Change")].call(calo, c, c.value)
                calo.run.apply(calo)
            }
        })

        links.forEach(l => {
            l.onclick = function (e) {
                e.preventDefault();
                if (calo.navigate) {
                    var a = l.getAttribute("@Link")
                    calo.navigate(a)
                }
            }
        })

        root.querySelectorAll("[\\@Show]").forEach(el => {
            const field = el.getAttribute("@Show")
            const flag = calo.model[field]
            el.style.display = flag === true ? '' : 'none'
        })

        applySameFieldKeyupChange("input[type=text]")
        applySameFieldKeyupChange("textarea")
        applySameFieldKeyupChange("input[type=date]")
        applySameFieldKeyupChange("input[type=password]")
        applySameFieldKeyupChange("input[type=number]")
        applySameFieldClickChange("input[type=checkbox]")
        applySameFieldClickChange("input[type=radio]", function (el) {
            if (el.name) {
                const groupname = el.name
                const group = root.querySelectorAll(`input[type=radio][name=${groupname}]`)
                group.forEach(el1 => {
                    if (!el1.isEqualNode(el)) {
                        const sen = el1.dataset.identity + "=false"
                        eval(sen);
                    }
                })
            }

        })
        applySameFieldSelectChange()



        function applySameFieldKeyupChange(tag) {
            root.querySelectorAll(tag).forEach(ip => {
                if (window.addEventListener) {
                    ip.addEventListener('keyup', function (e) {
                        e.preventDefault()
                        e.stopPropagation()
                        eval(ip.dataset.identity + "='" + ip.value + "'")
                        root.querySelectorAll(`[data-identity= '${ip.dataset.identity}']`).forEach(el => {
                            SetValue(el, ip.value)
                        })

                    }, false);
                } else {
                    ip.attachEvent('change', function () {
                        log(5);
                    });
                }
            })
        }

        function applySameFieldClickChange(tag, pre) {
            root.querySelectorAll(tag).forEach(ipc => {
                ipc.onclick = function () {
                    if (pre) pre(this)
                    var val = this.checked
                    var identity = this.dataset.identity
                    eval(identity + "=" + val + "")
                    const nodes = root.querySelectorAll(`[data-identity= '${identity}']`);
                    nodes.forEach(el => {
                        SetValue(el, val)
                    })

                }
            })
        }

        function applySameFieldSelectChange() {
            root.querySelectorAll("Select").forEach(ipc => {
                ipc.onchange = function () {
                    const selected = this.value
                    var identity = this.dataset.identity
                    eval(identity + "='" + selected + "'")
                    const nodes = root.querySelectorAll(`[data-identity= '${identity}']`);
                    nodes.forEach(el => {
                        SetValue(el, selected)
                    })

                }
            })
        }

    }

    function SetValue(el, val) {
        if (el.tagName === "INPUT" && el.type === "text") el.value = val
        else if (el.tagName === "INPUT" && el.type === "password") el.value = val
        else if (el.tagName === "INPUT" && el.type === "date") el.value = val
        else if (el.tagName === "INPUT" && el.type === "number") el.value = val
        else if (el.tagName === "TEXTAREA") el.value = val
        else if (el.tagName === "A") el.href = val
        else if (el.tagName === "INPUT" && el.type === "checkbox")
            el.checked = val
        else if (el.tagName === "INPUT" && el.type === "radio") el.checked = val
        else if (el.tagName === "BUTTON") {
            const dataName = el.getAttribute("@field")
            el.dataset[dataName] = val
        } else if (["LABEL", "SPAN", "OPTION", "H2", "H1", "H3", "P", "DIV", "LI"].indexOf(el.tagName) !== -1) {
            console.log(el.tagName);
            el.innerHTML = val
        } else if (el.tagName === 'SELECT') {
            el.value = val
        }

    }

    function getElsByFieldName(scope, fieldName) {
        return scope.querySelectorAll("[\\@field='" + fieldName + "'")
    }

    function isValType(obj) {
        return typeof obj === "number" || typeof obj === "string" || typeof obj === "boolean"
    }

    function isArrayType(obj) {
        return obj instanceof Array
    }

    function isObjectType(obj) {
        return !(obj instanceof Array) && typeof obj === "object"
    }

    function getDataValByidentity(el) {
        let identity = el.dataset.identity
        return eval(identity)
    }

    calo.getElsByidentity = getElsByidentity

    function getElsByidentity(identity) {
        return root.querySelectorAll(`[data-identity= '${identity}']`)
    }

    calo.$ = $

    function $(id) {
        return document.getElementById(id)
    }
    window.calo.makePlugin = function (idOrEl, functionPlugin) {
        if (idOrEl instanceof HTMLElement)
            functionPlugin.call(calo, idOrEl)
        else
            functionPlugin.call(calo, $(idOrEl))
        calo.run.apply(calo)
    }
    window.calo.callPlugin = function (el, functionPlugin) {
        let args = [el]
        for (let i = 2; i < arguments.length; i++) {
            args.push(arguments[i])
        }
        functionPlugin.apply(calo, args)
        calo.run.apply(calo)
    }

    function insertAfter(newElement, targetElement) {
        var parent = targetElement.parentNode;
        if (parent.lastChild == targetElement) {
            parent.appendChild(newElement);
        } else {
            parent.insertBefore(newElement, targetElement.nextSibling);
        }
    }
    calo.ajax = ajax
    calo.ajaxJson = ajaxJson
    calo.ajaxQueue = ajaxQueue

    function ajaxQueue(req) {
        if (!ajaxQueue.queue)
            ajaxQueue.queue = []
        if (req)
            ajaxQueue.queue.push(req)
        if (ajaxQueue.queue.length > 0) {
            const {
                url,
                data,
                type,
                success,
                error
            } = ajaxQueue.queue[0]
            ajax({
                url: url,
                data: data,
                type: type,
                success: function (resp) {
                    success(resp)
                    ajaxQueue.queue.shift()
                    ajaxQueue()

                },
                error: function () {
                    error()
                    ajaxQueue()
                }
            })
        }
    }


    function ajaxJson({
        url,
        data,
        type,
        success,
        error
    }, beforeSend) {
        type = type || "get";
        data = data || {};
        let str = "";
        for (let i in data) {
            str += `${i}=${data[i]}& `;
        }
        str = str.slice(0, str.length - 1);
        if (type === "get") {
            var d = new Date();
            url = url + "?" + str + "&__qft=" + d.getTime();
        }
        let xhr = new XMLHttpRequest();
        xhr.open(type, url, true);
        if (beforeSend) beforeSend(xhr);
        if (type === "get") {
            xhr.send();
        } else if (type === "post") {
            xhr.setRequestHeader("Content-type", "application/json");
            xhr.send(JSON.stringify(data));
        }
        xhr.onload = function () {
            if (xhr.status === 200) {
                success.call(calo, JSON.parse(xhr.responseText))
                calo.run.apply(calo);
            } else {
                error && error(xhr.status);
            }
        }
    }

    function ajax({
        url,
        data,
        type,
        success,
        error
    }, beforeSend) {
        type = type || "get";
        data = data || {};
        let str = "";
        for (let i in data) {
            str += `${i}=${data[i]}& `;
        }
        str = str.slice(0, str.length - 1);
        if (type === "get") {
            var d = new Date();
            url = url + "?" + str + "&__qft=" + d.getTime();
        }
        let xhr = new XMLHttpRequest();
        xhr.open(type, url, true);
        if (beforeSend) beforeSend(xhr);
        if (type === "get") {
            xhr.send();
        } else if (type === "post") {
            xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
            xhr.send(str);
        }
        xhr.onload = function () {
            if (xhr.status === 200) {
                success.call(calo, JSON.parse(xhr.responseText))
                calo.run.apply(calo);
            } else {
                error && error(xhr.status);
            }
        }
    }
})(window.calo);


//spa

(function (o) {
    o.spaPath = o.spaPath || "./"
    const root = o.rootel.querySelector("[\\@Router]");
    o.routes = {} || o.routes
    o.templateStore = {} || o.templateStore

    var stringToHTML = function (str) {
        var parser = new DOMParser();
        var doc = parser.parseFromString(str, 'text/html');
        return doc.body;
    };
    o.navigate = function (route, isHistory) {
        if (!window.templateLoaded && route !== "/") {
            console.log("only route template is loaded, rquesting others cannot succeed")
            return
        }
        if (isHistory) {
            window.history.pushState({
                route
            }, '', route)
        } else {
            location.href = location.href.split('#')[0] + '#' + route
        }
        if (route.indexOf('?') !== -1) {
            calo.query = getQueryJson(route)
            const parts = route.split('?')
            route = parts[0]
        } else {
            calo.query = {}
        }
        root.innerHTML = ''
        var hm = stringToHTML(decodeURI(calo.templateStore[route.toLowerCase()]))
        var scriptBlock = hm.getElementsByTagName('script')[0]
        var script = scriptBlock ? scriptBlock.text : ''
        root.appendChild(hm)
        if (script)
            eval(script)


    }

    o.router = function (routes, next) {
        var router = calo.routes || {}
        router = {
            ...router,
            ...routes
        }
        const templateStore = o.templateStore || {}
        var proms = []
        for (const key in router) {
            if (Object.hasOwnProperty.call(router, key)) {
                const keyLower = key.toLowerCase()
                var p = new Promise(resolve => {
                    const htmlName = router[key];
                    getHtmlOrJson(o.spaPath + htmlName + "?_=" + Math.random(), function (text) {
                        templateStore[keyLower] = encodeURI(text);
                        resolve()
                    })

                })
                proms.push(p)
            }
        }
        Promise.all(proms).then(function () {
            window.templateLoaded = true
            console.log('all templates has been loaded')
            if (next)
                next()
        })
    }
    window.calo = o || {
        model: {}
    }

})(window.calo);




//plugins

function pluginLogo(el) {
    el.innerHTML = "Hello Calo"
    this.model.user = 'me'
}

function pluginUpload(container) {
    var form = document.createElement('div')
    var upload = document.createElement('input')
    upload.type = 'file'
    form.appendChild(upload)
    var btnUpload = document.createElement('button')
    btnUpload.innerHTML = "upload"
    btnUpload.onclick = function () {
        var formdata = new FormData();
        formdata.append("upload_file", upload.files[0]) // 前面的"upload_file"是后端获取文件的key，后面的参数是获取文件的(两个列表，都获取第一个)
        $.ajax({
            url: "/ajax_upload",
            type: "POST",
            data: formdata,
            contentType: false,
            processData: false,
            success: function (data) {
                if (data["code"] == "200") {
                    alert(data["message"])
                }

            },
            error: function (data) {
                alert("处理失败")
            }

        })
    }
    form.appendChild(btnUpload)
    container.appendChild(form)
}

function pluginTree(container) {
    const calo = this
    var root = {
        id: 0,
        pid: null,
        name: 'CMS'
    }
    var nodes = [root, {
        id: 1,
        pid: 0,
        name: 'pages'
    }]
    nodes.push({
        id: 2,
        pid: 1,
        name: 'contentsManager',
        url: '/contentsManager'
    })
    nodes.push({
        id: 3,
        pid: 1,
        name: 'contentsave',
        url: '/contentsave'
    })
    nodes.push({
        id: 4,
        pid: 1,
        name: 'register',
        url: '/register'
    })
    nodes.push({
        id: 5,
        pid: 1,
        name: 'createorder',
        url: '/createorder'
    })

    function renderObject(node, nodes) {
        var csh = ''
        nodes.filter(n => {
            return n.pid == node.id
        }).forEach(
            c => {
                csh += `<li data-url=${c.url}>${c.name}${renderObject(c, nodes)}</li>`
            }
        )
        return csh == '' ? '' : `<ul>${csh}</ul>`
    }
    let obj = renderObject({
        id: null
    }, nodes)
    container.innerHTML = obj

    let els = container.getElementsByTagName('li');
    for (const el in els) {
        els[el].onclick = function (e) {
            e.preventDefault()
            e.stopPropagation()
            if (e.target.dataset.url !== 'undefined')
                calo.navigate(e.target.dataset.url)
            if (this.children[0])
                this.children[0].style.display = this.children[0].style.display == 'none' ? '' : 'none';
            calo.callPlugin(el, function () {
                this.model.user = 22
            })
        }
    }
    container.getElementsByTagName('ul')[0].style.display = ''

}

const plugins = {
    pluginLogo,
    pluginTree
}

for (const i in plugins) {
    window[i] = plugins[i]
}